﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Expect.Core.Reports
{
    /// <summary>
    /// Read a specification tree from an assembly
    /// </summary>
    public class AssemblyReader
    {
        private Assembly _ass;

        private Action<string> _logcallback;

        private AssemblyReader(Assembly ass, Action<string> logcallback)
        {
            _ass = ass;
            _logcallback = logcallback;
        }

        /// <summary>
        /// Read a specification tree from an assembly
        /// </summary>
        /// <param name="assfile"></param>
        /// <param name="logcallback"></param>
        /// <returns></returns>
        public static SpecificationDefinition ReadFrom(string assfile, Action<string> logcallback)
        {
            return new AssemblyReader(Assembly.LoadFrom(assfile), logcallback).ReadSpecificationTree();
        }

        /// <summary>
        /// Read a specification tree from an assembly
        /// </summary>
        /// <param name="ass"></param>
        /// <param name="logcallback"></param>
        /// <returns></returns>
        public static SpecificationDefinition ReadFrom(Assembly ass, Action<string> logcallback)
        {
            return new AssemblyReader(ass, logcallback).ReadSpecificationTree();
        }

        private void Log(string msg)
        {
            if (_logcallback != null) _logcallback(msg);
        }

        private SpecificationDefinition ReadSpecificationTree()
        {
            SpecificationDefinition root = new SpecificationDefinition();
            var types = _ass.GetTypes().OrderBy(t => t.Name).OrderBy(t => t.Namespace);
            
            var namespaces = types.Select(t => t.Namespace).Distinct();
            NamespaceTreeItem rootitem = new NamespaceTreeItem();
            NamespaceTreeItem lastitem = null;
            
            foreach (var ns in namespaces)
            {
                var item = new NamespaceTreeItem();
                item.Namespace = ns;
                item.Types = types.Where(t => t.Namespace == ns);
                
                if (lastitem != null && item.Namespace.StartsWith(lastitem.Namespace + "."))
                {
                    lastitem.AddChild(item);
                }
                else
                {                    
                    AddToParent(rootitem, lastitem, item);
                }
                item.Initialize();
                lastitem = item;
                /*
                Console.WriteLine(ns);
                var nsTypes = types.Where(t => t.Namespace == ns);

                foreach (var type in nsTypes)
                {
                    Console.WriteLine("\t" + type.FullName);
                }*/
            }

            this.LogItems(rootitem, "");
            
            rootitem.AddToSpecification(root);

            this.LogSpec(root, "");
           
            return root;
        }

        private void LogItems(NamespaceTreeItem item, string prefix)
        {
            Log(prefix + "Namespace : " + item.Namespace + "(" + item.Name + ")");
            if (item.TypesWithSpecs != null)
            {
                foreach (var type in item.TypesWithSpecs)
                {
                    Log(prefix + " type: " + type.Value.FullName + "(" + type.Key + ")");
                }
            }

            foreach (var child in item.Childs)
            {
                this.LogItems(child, prefix + "\t");
            }
        }

        private void LogSpec(SpecificationDefinition item, string prefix)
        {
            Log(prefix + "Spec : " + item.Name);
            foreach (var child in item.ChildSpecs)
            {
                this.LogSpec(child, prefix + "\t");
            }
        }

        private void AddToParent(NamespaceTreeItem rootitem, NamespaceTreeItem lastitem, NamespaceTreeItem item)
        {
            if (lastitem == null)
            {
                rootitem.AddChild(item);
                return;
            }

            NamespaceTreeItem parent = lastitem.Parent;
            while (parent.Parent != null && !item.Namespace.StartsWith(parent.Namespace))
            {
                parent = parent.Parent;
            }
            parent.AddChild(item);
        }

        
    }
}
